import json
from time import sleep

from selenium.webdriver import ActionChains
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

from setting import *
from tools.log import logger
from tools.yaml_reader import YamlReader


class BasePage:
    url = None
    driver: WebDriver = None

    # 在子类中需要重写，获取通用配置文件中，具体项目的元素配置文件字段
    page_elements = {}

    # 缓存动态读取 yaml 元素配置文件的解析结果
    page_elements_pool = {}

    def _locator(self, expression: str = "login_page.username_loc"):
        """
        解析元素表达式的方法
        :param expression: login_page.yml的解析结果.user_name  ==> login_page.username
        :return:
        """
        # 以 "." 分割 login_page.username
        logger.info("定位元素，使用的表达式为：{}".format(expression))
        name, value = expression.split(".")

        # 如果 name（页面元素 yaml 文件对应路径的 key） 在 setting.py 配置文件中找不到，抛出异常
        if name not in self.page_elements:
            raise Exception("元素配置文件的别名：{} 无法识别！！！".format(name))

        # 判断 name 是否已经缓存在 self.page_elements_pool 中
        if name not in self.page_elements_pool:
            self.page_elements_pool[name] = YamlReader(self.page_elements[name]).data
            # 判断 定位方式 是否属于 selenium 支持的定位方式
            if (locator := self.page_elements_pool[name][value])[0] not in BY_RULES:
                raise Exception("无法识别的定位方法：{}".format(locator))

            return locator

        return self.page_elements_pool[name][value]

    def find_element(self, loc: str):
        """
        定位单个元素
        :param loc:
        :return:
        """
        WebDriverWait(self.driver, 20, 0.5).until(
            expected_conditions.element_to_be_clickable(element := self._locator(loc))
        )
        return self.driver.find_element(*element)

    def find_elements(self, loc: str):
        """
        定位多个元素
        :param loc:
        :return:
        """
        return self.driver.find_elements(*self._locator(loc))

    def get(self, url):
        """
        浏览器打开指定的 url
        :param url:
        :return:
        """
        logger.info("浏览器打开 url：{}".format(url))
        self.driver.get(url)

    def goto_page_by_url(self, url):
        logger.info("通过 url 打开页面，url：{}".format(url))
        sleep(3)
        self.driver.get(url)
        sleep(3)

    @property
    def current_url(self):
        """
        获取当前 url
        :return:
        """
        url = self.driver.current_url
        logger.info("浏览器当前访问的 url 是：{}".format(url))
        return url

    def back(self):
        """
        浏览器后退操作
        :return:
        """
        logger.info("浏览器执行 后退 操作")
        self.driver.back()

    def forward(self):
        """
        浏览器前进操作
        :return:
        """
        logger.info("浏览器执行 前进 操作")
        self.driver.forward()

    def refresh(self):
        """
        浏览器刷新操作
        :return:
        """
        logger.info("浏览器执行 刷新 操作")
        self.driver.refresh()

    @property
    def title(self):
        """
        获取当前页面标题
        :return:
        """
        title = self.driver.title
        logger.info("当前页面标题是：{}".format(title))
        return title

    def close(self):
        """
        关闭浏览器 tab
        :return:
        """
        logger.info("浏览器关闭当前 tab")
        self.driver.close()

    def quit(self):
        """
        退出浏览器
        :return:
        """
        logger.info("退出浏览器")
        self.driver.quit()

    @property
    def current_window(self):
        """
        获取当前窗口的 handle（句柄）
        :return:
        """
        window = self.driver.current_window_handle
        logger.info("当前浏览器窗口句柄是：{}".format(window))
        return window

    def switch_to_window(self, window_handle):
        """
        切换到指定窗口
        :param window_handle:
        :return:
        """
        logger.info("切换浏览器窗口，新的窗口句柄是：{}".format(window_handle))
        self.driver.switch_to.window(window_handle)

    def create_new_window_and_switch(self):
        """
        打开并切换至新的浏览器窗口
        :return:
        """
        logger.info("浏览器打开并切换至新窗口")
        self.driver.switch_to.new_window("window")

    def create_new_tab_and_switch(self):
        """
        打开并切换至新 tab
        :return:
        """
        logger.info("浏览器打开并切换至新 tab")
        self.driver.switch_to.new_window("tab")

    def switch_to_frame(self, iframe):
        """
        切换至 frame
        原生支持页面元素：id，name，索引
        :param iframe:
        :return:
        """
        logger.info("浏览器切换至新 iframe：{}".format(iframe))
        self.driver.switch_to.frame(iframe)

    def leave_frame(self):
        """
        退出 frame
        :return:
        """
        logger.info("浏览器退出 iframe")
        self.driver.switch_to.default_content()

    def get_window_size(self):
        """
        获取窗口大小
        :return:
        """
        logger.info("获取当前窗口尺寸")
        size = self.driver.get_window_size()
        width = size.get("width")
        height = size.get("height")
        logger.info("窗口宽度为：{}".format(width))
        logger.info("窗口高度为：{}".format(height))
        return width, height

    def set_window_size(self, width, height):
        """
        设置窗口尺寸
        :param width:
        :param height:
        :return:
        """
        logger.info("设置当前浏览器窗口尺寸")
        logger.info("浏览器窗口尺寸宽度为：{}".format(width))
        logger.info("浏览器窗口尺寸高度为：{}".format(height))
        self.driver.set_window_size(width, height)

    def get_window_position(self):
        """
        获取窗口位置
        :return:
        """
        logger.info("获取当前窗口坐标")
        position = self.driver.get_window_position()
        x = position.get("x")
        y = position.get("y")
        logger.info("窗口坐标 x={}".format(x))
        logger.info("窗口坐标 y={}".format(y))
        return x, y

    def set_window_position(self, x, y):
        """
        设置窗口位置
        :param x:
        :param y:
        :return:
        """
        logger.info("设置当前窗口坐标")
        logger.info("窗口坐标 x={}".format(x))
        logger.info("窗口坐标 y={}".format(y))
        self.driver.set_window_position(x, y)

    def maximize_window(self):
        """
        窗口最大化
        :return:
        """
        logger.info("浏览器窗口最大化")
        self.driver.maximize_window()

    def minimize_window(self):
        """
        浏览器窗口最小化
        :return:
        """
        logger.info("浏览器窗口最小化")
        self.driver.minimize_window()

    def fullscreen_window(self):
        """
        窗口全屏
        :return:
        """
        logger.info("浏览器全屏")
        self.driver.fullscreen_window()

    def save_screenshot(self, path):
        """
        截图
        :param path:
        :return:
        """
        logger.info("浏览器截图，保存路径：{}".format(path))
        self.driver.save_screenshot(path)

    def save_element_screen(self, element, path):
        """
        单个元素截图
        :param element:
        :param path:
        :return:
        """
        logger.info("单个元素截图，元素->{}，保存路径：{}".format(element, path))
        element.save_screenshot(path)

    def click(self, loc: str):
        """
        点击元素
        :param loc:
        :return:
        """
        logger.info("在元素：{} 上执行鼠标点击".format(loc))
        self.find_element(loc).click()

    def send_keys(self, loc: str, text):
        """
        输入文本内容
        :param loc:
        :param text:
        :return:
        """
        logger.info("在元素 {} 中输入内容：{}".format(loc, text))
        self.find_element(loc).send_keys(text)

    def clear(self, loc: str):
        """
        清除指定输入框中输入的内容
        :param loc:
        :return:
        """
        logger.info("清空元素 {} 中输入的内容".format(loc))
        self.find_element(loc).clear()

    def double_click(self, loc: str):
        """
        鼠标双击操作
        :param loc:
        :return:
        """
        logger.info("在元素 {} 执行鼠标双击操作")
        element = self.find_element(loc)
        ActionChains(self.driver).double_click(element).perform()

    def drag_and_drop(self, source_loc: str, target_loc: str):
        """
        元素拖放操作
        :param source_value:
        :param target_value:
        :return:
        """
        source_element = self.find_element(source_loc)
        target_element = self.find_element(target_loc)
        logger.info("把元素 {} 拖动到元素 {} 的所在位置".format(source_element, target_element))
        ActionChains(self.driver).drag_and_drop(
            source_element, target_element
        ).perform()

    def drag_and_drop_by_offset(self, source_loc: str, x_offset, y_offset):
        """
        通过坐标确定目标位置的拖放操作
        :param source_loc:
        :param x_offset:
        :param y_offset:
        :return:
        """
        logger.info("把元素 {} 拖动水平位移：{}；垂直位移：{}".format(source_loc, x_offset, y_offset))
        ActionChains(self.driver).drag_and_drop_by_offset(
            source_loc, x_offset, y_offset
        ).perform()

    def move_to_element(self, loc: str):
        """
        把鼠标移动到指定元素上
        :param loc:
        :return:
        """
        target_element = self.find_element(loc)
        logger.info("把鼠标移动到元素：{}".format(loc))
        ActionChains(self.driver).move_to_element(target_element).perform()

    def move_by_offset(self, x_offset, y_offset):
        """
        把鼠标移动到指定坐标位置
        :param x_offset:
        :param y_offset:
        :return:
        """
        logger.info("把鼠标移动水平位移：{}，垂直位移：{}".format(x_offset, y_offset))
        ActionChains(self.driver).move_by_offset(x_offset, y_offset).perform()

    def add_cookies_from_file(self, path):
        """
        从文件中读取 cookies，并使其生效
        :param path:
        :return:
        """
        with open(path, "r") as fp:
            cookies = json.load(fp)
            for cookie in cookies:
                cookie.pop("domain")
                logger.info("增加 cookie：{}".format(cookie))
                self.driver.add_cookie(cookie)

    def get_text(self, loc: str):
        """
        获取指定元素的文本
        :param loc:
        :return:
        """
        target_element = self.find_element(loc)
        logger.info("获取元素的文本，元素：{}，文本内容：{}".format(loc, text := target_element.text))
        return text
